package core;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;

import protocal.*;

public class GimCore {

	public static GimCore core;
	
	/**
	 * socket读超时
	 */
	private final int SOCK_TIME_OUT = 6 * 60 * 1000;
	
	/**
	 * tcp socket
	 */
	private Socket sock;
	
	/**
	 * socket输入流
	 */
	private InputStream in;
	
	/**
	 * socket输出流
	 */
	private OutputStream out;
	
	/**
	 * 数据库操作对象
	 */
	private GimDatabase gdb;

	/**
	 * 消息回调处理类
	 */
	private GimMessageHandle handle;
	
	/**
	 * 消息发送队列
	 */
	private ConcurrentLinkedQueue<Message> sendQueue;
	
	/**
	 * 消息存储队列
	 */
	private ConcurrentLinkedQueue<Message> storeQueue;
	
	/**
	 * 服务器pong时间
	 */
	private long lastPongTime;

	/**
	 * 存储线程
	 */
	private StoreProcessor storeProcessor;
	
	/**
	 * 接收服务器消息线程
	 */
	private ReceiveProcessor receiveProcessor;
	
	/**
	 * 心跳维持线程
	 */
	private HeartbeatProcessor hbProcessor;
	
	/**
	 * 发送消息线程
	 */
	private SendProcessor sendProcessor;
	
	/**
	 * 用户ID
	 */
	private long uid;
	
	/**
	 * 用户TOKEN
	 */
	private String token;
	
	/**
	 * 连接服务器host
	 */
	private String host;
	
	/**
	 * 连接服务器端口
	 */
	private int port;
	
	private GimCore(){
		//初始化队列
		sendQueue = new ConcurrentLinkedQueue<Message>();
		storeQueue = new ConcurrentLinkedQueue<Message>();
		
		//启动存储消息线程
		gdb = new GimDatabase();
		gdb.init();
		storeProcessor = new StoreProcessor(gdb, storeQueue);
		storeProcessor.start();
	}
	
	public static GimCore getInstance() {
		if(core == null) {
			core = new GimCore();
		}
		
		return core;
	}
	
	/**
	 * 注册消息处理回调
	 * @param handle
	 */
	public void registerHandler(GimMessageHandle handle) {
		this.handle = handle;
	}
	
	/**
	 * 启动GIM引擎
	 * @param host
	 * @param port
	 * @param uid
	 * @param token
	 * @return
	 */
	public synchronized boolean start(String host, int port, long uid, String token) {
		try {
			if(sock != null) {
				return true;
			}
			
			this.handle.handleConnectServerStart();
			
			this.host = host;
			this.port = port;
			
			//连接socket
			sock = new Socket(host, port);
			sock.setTcpNoDelay(true);
			sock.setSoTimeout(SOCK_TIME_OUT);
			sock.setKeepAlive(true);
			
			in = sock.getInputStream();
			out = sock.getOutputStream();
			
			this.handle.handleConnectServerSuccess();
			
			this.handle.handleAuthServerStart();
			
			Message m = new Message();
			m.cmd = Constants.MSG_AUTH_TOKEN;
			
			AuthenticationToken at = new AuthenticationToken();
			at.uid = uid;
			at.token = token;
			at.platform_id = Constants.PLATFORM_ANDROID;
			at.device_id = UUID.randomUUID().toString();
			
			m.body = at;
			//发送认证
			if(!send(m)) {
				this.handle.handleAuthServerFailed();
				core.close();
				return false;
			}
			
			Message m1 = receive();
			if(m1 == null || m1.cmd != Constants.MSG_AUTH_STATUS){
				this.handle.handleAuthServerFailed();
				core.close();
				return false;
			}
			
			AuthenticationStatus as = (AuthenticationStatus) m1.body;
			if(as.status != 0){
				this.handle.handleAuthServerFailed();
				core.close();
				return false;
			}
			
			this.uid = uid;
			this.token = token;
			this.lastPongTime = System.currentTimeMillis();
			
			this.handle.handleAuthServerSuccess();
			
			//开始loop
			startLoop();
			
			return true;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			this.handle.handleConnectServerFailed();
			return false;
		}
	}
	
	/**
	 * 重启GIM引擎，各种网络错误都可以进行重启
	 * @return
	 */
	public boolean restart() {
		
		close();
		return start(this.host, this.port, this.uid, this.token);
	}
	
	/**
	 * 启动接收消息，发送消息，心跳线程
	 */
	private void startLoop() {
		receiveProcessor = new ReceiveProcessor(core, handle);
		receiveProcessor.start();
		
		hbProcessor = new HeartbeatProcessor(core);
		hbProcessor.start();
		
		sendProcessor = new SendProcessor(core, sendQueue);
		sendProcessor.start();
	}
	
	/**
	 * 关闭资源
	 */
	public void close() {
		try {
			if(receiveProcessor != null) {
				receiveProcessor.interrupt();
			}
			
			if(sendProcessor != null) {
				sendProcessor.interrupt();
			}
			
			if(hbProcessor != null) {
				hbProcessor.interrupt();
			}
	
			if(in != null) {
				in.close();
			}
			
			if(out != null) {
				out.close();
			}
			
			if(sock != null) {
				sock.close();
				sock = null;
			}
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * GimCore实例销毁
	 */
	public void destroy() {
		
		close();
		
		if(storeProcessor != null) {
			storeProcessor.interrupt();
		}
		
		gdb.close();
		core = null;
	}
	
	/**
	 * 发送消息
	 * @param receiver
	 * @param content
	 * @param extra
	 * @return
	 */
	public boolean sendMessage(long receiver, String content, String extra) {
		
		if(receiver <= 0 || content.equals("")) {
			return false;
		}
		
		Message m = new Message();
		m.cmd = Constants.MSG_IM;
		
		IMMessage im = new IMMessage();
		im.sender = this.uid;
		im.receiver = receiver;
		im.gid = 0;
		im.timestamp = 0;
		im.msgid = 0;
		im.content = content;
		im.extra = extra;
		im.seq = m.seq;
		
		m.body = im;
		
		messageInQueue(m);
		insertInQueue(m);
		return true;
	}
	
	/**
	 * 发送群组消息
	 * @param gid
	 * @param content
	 * @param extra
	 * @return
	 */
	public boolean sendGroupMessage(long gid, String content, String extra) {
		if(gid <= 0 || content.equals("")) {
			return false;
		}
		
		Message m = new Message();
		m.cmd = Constants.MSG_GROUP_IM;
		
		IMMessage im = new IMMessage();
		im.sender = this.uid;
		im.receiver = gid;
		im.gid = gid;
		im.timestamp = 0;
		im.msgid = 0;
		im.content = content;
		im.extra = extra;
		im.seq = m.seq;
		
		m.body = im;
		
		messageInQueue(m);
		insertInQueue(m);
		return true;
	}
	
	/**
	 * 消息进发送队列
	 * @param m
	 */
	public void messageInQueue(Message m){
		sendQueue.add(m);
	}
	
	/**
	 * 消息进入库队列
	 * @param m
	 */
	public void insertInQueue(Message m){
		storeQueue.add(m);
	}
	
	/**
	 * 从socket接收消息
	 * @return
	 */
	public Message receive() {		
		return MessageUtil.readMessage(in);
	}
	
	/**
	 * socket发送数据
	 * @param m
	 * @return
	 */
	public boolean send(Message m) {
		try {
			out.write(MessageUtil.writeMessage(m));
			out.flush();	
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	/**
	 * 获取服务器PONG时间
	 * @return
	 */
	public long getLastPongTime() {
		return lastPongTime;
	}

	/**
	 * 更新服务器PONG时间
	 * @param lastPongTime
	 */
	public void setLastPongTime(long lastPongTime) {
		this.lastPongTime = lastPongTime;
	}
	
	/**
	 * 获取数据库操作对象
	 * @return
	 */
	public GimDatabase getGdb() {
		return gdb;
	}
}
